package org.jinghouyu.http.proxy.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.jinghouyu.http.proxy.server.callback.RequestHandler;
import org.jinghouyu.http.proxy.utils.IOUtils;

/**
 * to parse client request and achieve address of server
 * then build a channel between client and server
 * @author liujingyu
 *
 */
public class ClientProxier {

	private static final Logger logger = Logger.getLogger(ClientProxier.class);
	private Socket clientSocket = null;
	private ProxyServer server = null;
	private boolean hasStart = false;
	
	ClientProxier(ProxyServer server, Socket socket) {
		this.clientSocket = socket;
		this.server = server;
	}
	
	synchronized void start() {
		if(!hasStart) {
			new Thread() {
				public void run() {
					_run();
				}
			}.start();
			hasStart = true;
		}
	}
	
	private void _run() {
		while(true) {
			Request request = doProxy();
			if(request == null || !request.isKeepAlive()) break;
		}
	}
	
	private Request doProxy() {
		InputStream clientIn = null;
		OutputStream clientOut = null;
		InputStream serverIn = null;
		OutputStream serverOut = null;
		Socket serverSocket = null;
		Request request = null;
		try {
			clientIn = clientSocket.getInputStream();
			request = Request.parse(clientSocket);
			if(logger.isDebugEnabled()) {
				logger.debug(request.toLogString());
			}
			request = renderRequest(request);
			if(logger.isDebugEnabled()) {
				logger.debug("request may be changed but not sure. " + request.toLogString());
			}
			serverSocket = connect(request);
			Channel channel = new Channel(this, request, clientSocket.getOutputStream(), serverSocket.getInputStream(), serverSocket.getOutputStream());
			channel.doChannel();
			return request;
		} catch (IOException e) {
			if(logger.isDebugEnabled()) {
				if(request != null) {
					try {
						logger.debug(request.toLogString() + " some error occurs due to ", e);
					} catch (UnknownHostException e1) {
					}
				} else {
					logger.debug(" some error occurs due to ", e);
				}
			}
			return null;
		} finally {
			IOUtils.closeQuietly(serverIn);
			IOUtils.closeQuietly(serverOut);
			IOUtils.closeQuietly(serverSocket);
			if(request == null || !request.isKeepAlive()) {
				IOUtils.closeQuietly(clientIn);
				IOUtils.closeQuietly(clientOut);
				IOUtils.closeQuietly(clientSocket);
			}
		}
	}
	
	private Socket connect(Request request) throws IOException {
		InetAddress address = request.getHostAddress();
		int port = request.getPort();
		if(logger.isDebugEnabled()) {
			logger.debug("connecting to " + address + ":" + port);
		}
		Socket socket =  new Socket(address, port);
		if(logger.isDebugEnabled()) {
			logger.debug("connecting to " + address + ":" + port + " successfully");
		}
		return socket;
	}
	
	private Request renderRequest(Request request) throws IOException {
		for(RequestHandler handler : getRequestHandler(request)) {
			request = handler.handle(request);
		}
		return request;
	}
	
	private List<RequestHandler> getRequestHandler(Request request) {
		ProxyServer server = getServer();
		List<RequestHandler> handlers = server.getRequestHandlers();
		List<RequestHandler> result = new ArrayList<>();
		if(handlers == null) return result;
		for(int i = 0; handlers != null && i < handlers.size(); i++) {
			RequestHandler handler = handlers.get(i);
			if(handler == null) continue;
			if(handler.match(request)) {
				result.add(handler);
			}
		}
		return result;
	}
	
	public ProxyServer getServer() {
		return this.server;
	}
}
